/*
 * $Id: dictClient.cpp 9ce65bed8ef4 2012/08/26 02:14:51 pgurumur $
 * Copyright (c) 2012 Prabhu Gurumurthy <pgurumur@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *

 * __version__  = "$Revision: 9ce65bed8ef4 $"
 * __author__   = "$Author: pgurumur $"
 * __modified__ = "$Date: 2012-08-26 02:14:51Z $"
 */
#include "dictClient.h"

namespace QtDict {
   
   dictClient::dictClient (
         const QString &Server, 
         const u_int16_t &Port = DictPort,
         const int &Af = AF_INET) : __af (Af), __sock (0), __conn (0),
                                    __port (Port)
   {
      __dictServer = Server;
      memset(&__sock4, 0, sizeof(__sock4));
      memset(&__sock6, 0, sizeof(__sock6));

      struct hostent *host = gethostbyname2(
            __dictServer.toAscii().data(), __af);
      if (host) {
         switch (__af) {
            case AF_INET:
               memcpy(&__sock4.sin_addr, host->h_addr_list[0], host->h_length);
               __sock4.sin_family = PF_INET;
               __sock4.sin_port = htons (__port);
               break;

            case AF_INET6:
               memcpy(&__sock6.sin6_addr, host->h_addr_list[0], host->h_length);
               __sock6.sin6_family = PF_INET6;
               __sock6.sin6_port = htons (__port);
               break;

            default:
               errx (1, "unknown address family: %d", __af);
               /* NOT REACHED */
         }
      }
      else
         err (1, "gethostbyname2()");

      int protocol = 0;
      struct protoent *proto;
      const char *DictProtocol = "tcp";
      if ((proto = getprotobyname(DictProtocol)) != NULL)
         protocol = proto->p_proto;

      __sock = socket (__af, SOCK_STREAM, protocol);
      if (__sock < 0)
         err (1, "socket()");

      int setopt = 0;
      setopt = setsockopt (__sock, SOL_SOCKET, SO_RCVBUF, &RecvBuffer,
            sizeof(u_int32_t));
      if (setopt < 0)
         err (1, "recv buffer: setsockop()");

      __setUnblock ();
   }


   dictClient::~dictClient ()
   {
      if (__sock)
         close (__sock);
   }

   dictClient::dictClient (const dictClient &dictc)
   {
      *this = dictc;
   }

   dictClient &
   dictClient::operator= (const dictClient &dictc)
   {
      __dictServer = dictc.__dictServer;
      __port = dictc.__port;
      __sock = dictc.__sock;
      __af = dictc.__af;
      __sock4 = dictc.__sock4;
      __sock6 = dictc.__sock6;

      return *this;
   }

   void
   dictClient::__setUnblock ()
   {
      int fcntlValue = fcntl(__sock, F_GETFL);
      if (fcntlValue < 0)
         err (1, "fcntl(fd, F_GETFL)");

      if (fcntlValue & O_NONBLOCK)
         warnx ("socket already set to nonblocking");
      else {
         fcntlValue |= O_NONBLOCK;
         if (fcntl(__sock, F_SETFL, fcntlValue) < 0)
            err (1, "fcntl(fd, F_SETFL)");
      }
   }

   void
   dictClient::__setBlock ()
   {
      int fcntlValue = fcntl(__sock, F_GETFL);
      if (fcntlValue < 0)
         err (1, "fcntl(fd, F_GETFL)");

      if (!(fcntlValue & O_NONBLOCK))
         warnx ("socket is already set to blocking");
      else {
         fcntlValue &= ~O_NONBLOCK;
         if (fcntl(__sock, F_SETFL, fcntlValue) < 0)
            err (1, "fcntl(fd, F_SETFL)");
      }
   }

   void
   dictClient::dictConnect ()
   {
      int retval = 0;
      switch (__af) {
         case AF_INET:
            retval = connect (__sock, 
                  (struct sockaddr *)&__sock4, sizeof(__sock4));
            if (errno != EINPROGRESS)
               err (1, "connect()");
            __conn = 1;
            break;

         case AF_INET6:
            retval = connect (__sock,
                  (struct sockaddr *)&__sock6, sizeof(__sock6));
            if (errno != EINPROGRESS)
               err (1, "connect()");
            __conn = 1;
            break;

         default:
            errx (1, "unknown address family");
            /* NOT REACHED */
      };

      __setBlock ();

      struct utsname name;
      memset(&name, 0, sizeof (struct utsname));

      if (uname (&name) != 0)
         err (1, "uname()");

      QString dictClientInfo = "client info ";
      dictClientInfo += name.sysname;
      dictClientInfo += name.release;
      dictClientInfo += "version ";
      dictClientInfo += DictClientVersion + "\r\n";

      if (writeSocket (dictClientInfo) < 0)
         err (1, "dictWrite()");
   }

   int
   dictClient::writeSocket (const QString &Buffer)
   {
      int retval = 0;
      const char *Data = Buffer.toAscii().data();
      struct pollfd pfd[1];

      pfd[0].fd = __sock;
      pfd[0].events = POLLOUT;

      int nfds = poll (pfd, 1, PollTimeout);
      if ((nfds == -1) || (pfd[0].revents & (POLLERR | POLLHUP | POLLNVAL)))
         retval = -1;

      if (nfds == 0)
         warnx ("poll(): timeout");

      /*
       * write to socket
       */
      retval = write (__sock, Data, strlen (Data));
      
      return retval;
   }

   int
   dictClient::readSocket (QString &Data)
   {
      int retval = 0;
      char buf[RecvBuffer];
      struct pollfd pfd[1];

      pfd[0].fd = __sock;
      pfd[0].events = POLLIN;

      int flag = 1;
      int nfds = 0;
      while (flag) {
         nfds = poll (pfd, 1, PollTimeout);
         if (nfds == 0)
            flag = 0;
         else {
            if ((nfds == -1) || (pfd[0].revents & (POLLERR|POLLHUP|POLLNVAL))) {
               retval = -1;
               flag = 0;
            }

            int length = read (__sock, buf, sizeof (buf));
            if (length < 0) {
               flag = 0;
               retval = -1;
            }
            else
               Data += buf;
         }
      }

      return retval;
   }
}
